/*
 
 RFA Manager, Refractor2 Archive editor.
 Copyright (C) 2008
 Torsten Kammer         torsten@ferroequinologist.de
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 */

#import "RFADragAndDropDelegate.h"
#import "MyDocument.h"
#import "ImageAndTextCell.h"
#import "RFATreeNode.h"
#import "RFAArchive.h"
#import "RFABrowserCell.h"
#import "PropertyCategories.h"
#import "NSArrayRFAAdditions.h"
#import "RFAPasteboardOwner.h"

static NSString *RFAPasteboardType = @"com.google.code.bmmt.rfamanager.pasteboard";

@interface RFADragAndDropDelegate ()

- (BOOL)_writeNodes:(NSArray *)items toPasteboard:(NSPasteboard *)pboard;

- (NSDragOperation)_validateDrop:(id <NSDraggingInfo>)dragInfo toNode:(RFATreeNode *)node index:(NSUInteger)index;
- (BOOL)_acceptDrop:(id <NSDraggingInfo>)dragInfo toNode:(RFATreeNode *)node index:(NSInteger)index;

@end

@implementation RFADragAndDropDelegate

- (id)initWithDocument:(MyDocument *)aDocument;
{
    if (![super init]) return nil;
    
    self.document = aDocument;
    
    document.outlineView.dataSource = self;
	[document.outlineView setDraggingSourceOperationMask:NSDragOperationDelete | NSDragOperationCopy forLocal:NO];
	[document.outlineView setDraggingSourceOperationMask:NSDragOperationDelete | NSDragOperationCopy | NSDragOperationMove forLocal:YES];
    [document.outlineView registerForDraggedTypes:[NSArray arrayWithObjects:RFAPasteboardType, NSFilenamesPboardType, NSURLPboardType, nil]];
	
	document.browser.delegate = self;
	[document.browser setDraggingSourceOperationMask:NSDragOperationDelete | NSDragOperationCopy forLocal:NO];
	[document.browser setDraggingSourceOperationMask:NSDragOperationDelete | NSDragOperationCopy | NSDragOperationMove forLocal:YES];
    [document.browser registerForDraggedTypes:[NSArray arrayWithObjects:RFAPasteboardType, NSFilenamesPboardType, NSURLPboardType, nil]];
    
	document.searchView.dataSource = self;
	[document.searchView setDraggingSourceOperationMask:NSDragOperationDelete | NSDragOperationCopy forLocal:NO];
	[document.searchView setDraggingSourceOperationMask:NSDragOperationDelete | NSDragOperationCopy | NSDragOperationMove forLocal:YES];
    [document.searchView registerForDraggedTypes:[NSArray arrayWithObjects:RFAPasteboardType, NSFilenamesPboardType, NSURLPboardType, nil]];
	
	document.searchPathControl.delegate = self;
	[document.searchPathControl setDraggingSourceOperationMask:NSDragOperationDelete | NSDragOperationCopy forLocal:NO];
	[document.searchPathControl setDraggingSourceOperationMask:NSDragOperationDelete | NSDragOperationCopy | NSDragOperationMove forLocal:YES];
    [document.searchPathControl registerForDraggedTypes:[NSArray arrayWithObjects:RFAPasteboardType, NSFilenamesPboardType, NSURLPboardType, nil]];
	
    return self;
}

@synthesize document;

#pragma mark -

- (NSArray *)outlineView:(NSOutlineView *)outlineView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedItems:(NSArray *)items
{
	return [self.document namesForFiles:[items valueForKey:@"representedObject"] createdAt:dropDestination.path];
}

- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
{
    return [self _writeNodes:[items valueForKey:@"representedObject"] toPasteboard:pboard];
}

- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)targetItem proposedChildIndex:(NSInteger)index
{
	return [self _validateDrop:info toNode:[targetItem valueForKey:@"representedObject"] index:index];
}

- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
{
	return [self _acceptDrop:info toNode:[item valueForKey:@"representedObject"] index:index];
}

#pragma mark -

- (NSArray *)browser:(NSBrowser *)sender namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)columnIndex
{
	// Get the elements
	NSMatrix *matrix = [sender matrixInColumn:columnIndex];
	
	NSMutableArray *items = [NSMutableArray array];
	
	NSUInteger index = rowIndexes.firstIndex;
	do
	{
		RFABrowserCell *cell = [matrix cellAtRow:index column:0];
		[items addObject:cell.objectValue];
	}
	while ((index = [rowIndexes indexGreaterThanIndex:index]) != NSNotFound);
	
	return [self.document namesForFiles:items createdAt:dropDestination.path];
}

- (BOOL)browser:(NSBrowser *)sender canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)columnIndex withEvent:(NSEvent *)dragEvent
{
	return YES;
}

- (BOOL)browser:(NSBrowser *)sender writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)columnIndex toPasteboard:(NSPasteboard *)pasteboard
{
	// Get the elements
	NSMatrix *matrix = [sender matrixInColumn:columnIndex];
	
	NSMutableArray *items = [NSMutableArray array];
	
	NSUInteger index = rowIndexes.firstIndex;
	do
	{
		RFABrowserCell *cell = [matrix cellAtRow:index column:0];
		[items addObject:cell.objectValue];
	}
	while ((index = [rowIndexes indexGreaterThanIndex:index]) != NSNotFound);
	
	return [self _writeNodes:items toPasteboard:pasteboard];
}

- (void)browser:(NSBrowser *)sender willDisplayCell:(id)cell atRow:(NSInteger)row column:(NSInteger)column
{
	// Has to be implemented, though we don't do anything here.
}

- (NSDragOperation)browser:(NSBrowser *)sender validateDrop:(id <NSDraggingInfo>)dragInfo proposedRow:(NSInteger *)inout_dropReferenceRowIndex column:(NSInteger *)inout_dropColumnIndex dropOperation:(NSBrowserDropOperation *) inout_dropRowRelativeLocation
{
	NSInteger targetIndex = *inout_dropReferenceRowIndex;
	NSMatrix *matrix = [sender matrixInColumn:*inout_dropColumnIndex];
	if (!matrix) return NSDragOperationNone;
	if (targetIndex < 0 || targetIndex >= matrix.numberOfRows)
	{
		targetIndex = matrix.numberOfRows;
		*inout_dropRowRelativeLocation = NSBrowserDropAbove;
	}
	
	RFATreeNode *node = [[matrix cellAtRow:0 column:*inout_dropReferenceRowIndex] objectValue];
	if (*inout_dropRowRelativeLocation == NSBrowserDropAbove || node.isLeaf)
	{
		// Dropping on or above file, or above directory, means dropping on parent at index, and is
		// to be treated as an "above" drop.
		*inout_dropRowRelativeLocation = NSBrowserDropAbove;
		node = node.parent;
	}
	else
	{
		// Dropping on directory means dropping into it.
		targetIndex = 0;
	}
	
	return [self _validateDrop:dragInfo toNode:node index:targetIndex];
}

- (BOOL)browser:(NSBrowser *)sender acceptDrop:(id <NSDraggingInfo>)dragInfo atRow:(NSInteger)dropReferenceRowIndex column:(NSInteger)dropColumnIndex dropOperation:(NSBrowserDropOperation)dropRowRelativeLocation
{
	NSInteger targetIndex = dropReferenceRowIndex;
	NSMatrix *matrix = [sender matrixInColumn:dropColumnIndex];
	if (!matrix) return NO;
	if (targetIndex < 0 || targetIndex >= matrix.numberOfRows)
	{
		targetIndex = matrix.numberOfRows;
		dropRowRelativeLocation = NSBrowserDropAbove;
	}
	RFATreeNode *node = [[matrix cellAtRow:0 column:dropReferenceRowIndex] objectValue];
	if (dropRowRelativeLocation == NSBrowserDropAbove || node.isLeaf)
		// Dropping on or above file, or above directory, means dropping on parent at index, and is
		// to be treated as an "above" drop.
		node = node.parent;
	else
		// Dropping on directory means dropping into it.
		targetIndex = 0;
	
	return [self _acceptDrop:dragInfo toNode:node index:targetIndex];
}

#pragma mark -

- (NSArray *)tableView:(NSTableView *)aTableView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes
{
	// Warning: Using self as the key to get the represented object is a hack and may stop working in future releases.
	return [self.document namesForFiles:[[document.searchArrayController.arrangedObjects objectsAtIndexes:rowIndexes] valueForKey:@"self"] createdAt:dropDestination.path];
}

- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
{
	// Warning: Using self as the key to get the represented object is a hack and may stop working in future releases.
	return [self _writeNodes:[[document.searchArrayController.arrangedObjects objectsAtIndexes:rowIndexes] valueForKey:@"self"] toPasteboard:pboard];
}

- (NSDragOperation)tableView:(NSTableView *)aTableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)targetIndex proposedDropOperation:(NSTableViewDropOperation)operation
{
    if (targetIndex > aTableView.numberOfRows) return NSDragOperationNone;
    if (operation == NSTableViewDropAbove) [aTableView setDropRow:targetIndex dropOperation:NSTableViewDropOn];
    
    return [self _validateDrop:info toNode:[[document.searchArrayController.arrangedObjects objectAtIndex:targetIndex] valueForKey:@"self"] index:0];
}

- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id < NSDraggingInfo >)info row:(NSInteger)targetIndex dropOperation:(NSTableViewDropOperation)operation
{
    if (operation == NSTableViewDropAbove || targetIndex >= aTableView.numberOfRows) return NO;
    
    BOOL result = [self _acceptDrop:info toNode:[[document.searchArrayController.arrangedObjects objectAtIndex:targetIndex] valueForKey:@"self"] index:0];
    [self.document updateSearchPredicate];
    return result;
}

#pragma mark -

- (BOOL)pathControl:(NSPathControl *)pathControl shouldDragPathComponentCell:(NSPathComponentCell *)pathComponentCell withPasteboard:(NSPasteboard *)pasteboard
{
	RFATreeNode *node = nil;
	for (NSPathComponentCell *currentCell in pathControl.pathComponentCells)
	{
		if (!node)
		{
			if (![document.archive.root.name isEqual:currentCell.title]) return NO;
			node = document.archive.root;
		}
		else
			node = [node childWithName:currentCell.title];
		if (currentCell == pathComponentCell) break;
		if (!node) return NO;
	}
	
	if (!node) return NO;

	[self _writeNodes:[NSArray arrayWithObject:node] toPasteboard:pasteboard];
	
	return YES;
}

- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
{
	return [self.document namesForFiles:draggedNodes createdAt:dropDestination.path];
}

#pragma mark -

- (void)deleteDraggedNodes
{
	for (RFATreeNode *node in draggedNodes)
		[node.parent removeChildNamed:node.name];
	draggedNodes = nil;
}

- (void)clearDraggedNodes
{
	draggedNodes = nil;
}

#pragma mark -

- (BOOL)_writeNodes:(NSArray *)items toPasteboard:(NSPasteboard *)pboard;
{
	draggedNodes = items;
	if (items.count == 1)
    {
        RFATreeNode *node = items.firstObject;
        NSMutableArray *declaredPboardTypes = [NSMutableArray arrayWithObjects:NSFilesPromisePboardType, RFAPasteboardType, nil];
        if (node.isText)
            [declaredPboardTypes addObject:NSStringPboardType];
        else if (node.isImage)
            [declaredPboardTypes addObject:NSTIFFPboardType];
        
        [pboard declareTypes:declaredPboardTypes owner:self];
		[pboard addTypes:[NSArray arrayWithObject:NSFileContentsPboardType] owner:[[RFAPasteboardOwner alloc] initWithNode:node]];
        
		if (node.isLeaf)
			[pboard setPropertyList:[NSArray arrayWithObject:node.name.pathExtension] forType:NSFilesPromisePboardType];
		else
			[pboard setPropertyList:[NSArray arrayWithObject:NSFileTypeDirectory] forType:NSFilesPromisePboardType];
			
		[pboard setPropertyList:[NSDictionary dictionaryWithObjectsAndKeys:self.document.fileURL.absoluteString, @"rfaURL", [NSArray arrayWithObject:node.fullPath], @"nodePaths", nil] forType:RFAPasteboardType];
		
        if (node.isText)
            [pboard setString:node.contents forType:NSStringPboardType];
		else if (node.isImage)
			[pboard setData:node.image.TIFFRepresentation forType:NSTIFFPboardType];
    }
    else
    {
        [pboard declareTypes:[NSArray arrayWithObjects:NSFilesPromisePboardType, RFAPasteboardType, nil] owner:self];
        
		NSMutableArray *extensions = [NSMutableArray array];
		for (RFATreeNode *node in items)
		{
			NSString *type = nil;
			if (node.isLeaf)
				type = node.name.pathExtension;
			else
				type = NSFileTypeDirectory;
				
			if (![extensions containsObject:type])
				[extensions addObject:type];
		}
		[pboard setPropertyList:[NSDictionary dictionaryWithObjectsAndKeys:self.document.fileURL, @"rfaURL", [items valueForKey:@"fullPath"], @"nodePaths", nil] forType:RFAPasteboardType];
				
		[pboard setPropertyList:extensions forType:NSFilesPromisePboardType];
    }
    return YES;
}

- (NSDragOperation)_validateDrop:(id <NSDraggingInfo>)info toNode:(RFATreeNode *)node index:(NSUInteger)index;
{	
	// Dragging on file instead of folder?
	if (node.isLeaf) return NSDragOperationNone;
	
	NSString *type = [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:RFAPasteboardType, NSFilenamesPboardType, NSURLPboardType, nil]];
	if (!type) return NSDragOperationNone;
	if ([type isEqual:RFAPasteboardType])
	{
		// Is it legal to drop this?
		NSDictionary *rfapboarddata = [[info draggingPasteboard] propertyListForType:RFAPasteboardType];
		if ([[rfapboarddata objectForKey:@"rfaURL"] isEqual:self.document.fileURL.absoluteString])
		{
			// It's being dragged from us to us. See whether we try to move a parent to one of it's children
			for (NSString *path in [rfapboarddata objectForKey:@"nodePaths"])
				if ([node.fullPath hasPrefix:path])
					return NSDragOperationNone;
		}
		
		if ([info draggingSourceOperationMask] & NSDragOperationMove) return NSDragOperationMove;
		else if ([info draggingSourceOperationMask] & NSDragOperationCopy) return NSDragOperationCopy;
		return NSDragOperationNone;
	}
	return [info draggingSourceOperationMask] & NSDragOperationCopy;
}
- (BOOL)_acceptDrop:(id <NSDraggingInfo>)info toNode:(RFATreeNode *)node index:(NSInteger)index;
{
	NSString *type = [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:RFAPasteboardType, NSFilenamesPboardType, NSURLPboardType, nil]];
	if (!type) return NO;
	
	if (index < 0) index = 0; // Dragging directly on folder
	
	if ([type isEqual:RFAPasteboardType])
	{
		NSMutableArray *nodesToInsert = [NSMutableArray array];
		NSDictionary *data = [[info draggingPasteboard] propertyListForType:RFAPasteboardType];
		MyDocument *sourceDocument = (MyDocument *) [[NSDocumentController sharedDocumentController] documentForURL:[NSURL URLWithString:[data objectForKey:@"rfaURL"]]];
		RFAArchive *sourceArchive;
		NSError *error = nil;
		if (sourceDocument)
			sourceArchive = sourceDocument.archive;
		else
			sourceArchive = [[RFAArchive alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[data objectForKey:@"rfaURL"]] options:0 error:&error] error:&error];
		
		if (!sourceArchive)
		{
			if (error)
			{
				[[NSAlert alertWithError:error] beginSheetModalForWindow:[self.document windowForSheet] modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
			}
			return NO;
		}
		
		for (NSString *path in [data objectForKey:@"nodePaths"])
		{
			// root has a name itself, that has to be removed
			if (![sourceArchive.root.name isEqual:path.pathComponents.firstObject]) return NO;
			RFATreeNode *parentNode = [sourceArchive.root childWithPathArray:[path.pathComponents subarrayWithRange:NSMakeRange(1, path.pathComponents.count - 2)]];
			if (path.pathComponents.count == 2)
				parentNode = sourceArchive.root;
			if (!parentNode) return NO;
			RFATreeNode *node = [parentNode childWithName:path.lastPathComponent];
			if (!node) return NO;			
			[nodesToInsert addObject:node];
		}
		
		if ([info draggingSourceOperationMask] & NSDragOperationMove)
			[self.document startMovingNodes:nodesToInsert toNode:node atIndex:index];
		else
			[self.document startCoypingNodes:nodesToInsert toNode:node atIndex:index];
		
		return YES;
	}
	else
	{
		NSArray *filesToInsert = nil;
		if ([type isEqual:NSURLPboardType])
			filesToInsert = [NSArray arrayWithObject:[NSURL URLFromPasteboard:[info draggingPasteboard]].path];
		else
			filesToInsert = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
		
		[self.document startAddingFilesWithNames:filesToInsert toNode:node atIndex:index];
		
		return YES;
	}
}

@end
